﻿/*
 * Please leave this Copyright notice in your code if you use it
 * Written by Decebal Mihailescu [http://www.codeproject.com/script/articles/list_articles.asp?userid=634640]
 */
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace CommonUtils
{
    public class CustomRichTextBox : RichTextBox
    {
        //Utils.FreeTranslatorForm _parentForm;

        private IUIMisc ParentForm
        {
            get { return this.FindForm() as IUIMisc; }
        }
        SolidBrush _semiTransBrush;
        Font _fontOverlay;
        StringFormat _strformat;
        public CustomRichTextBox()
        {
            this.SetStyle(ControlStyles.DoubleBuffer | ControlStyles.AllPaintingInWmPaint | ControlStyles.Opaque, true);
            _semiTransBrush = new SolidBrush(Color.FromArgb(128, 128, 0, 0));
            _fontOverlay = new Font(this.Font.FontFamily, 2 * this.Font.Size);
            //TextFormatFlags flags = TextFormatFlags.VerticalCenter | TextFormatFlags.HorizontalCenter | TextFormatFlags.SingleLine;
            _strformat = new StringFormat(StringFormatFlags.NoClip);
            _strformat.Alignment = StringAlignment.Center;
        }
        void Dispose()
        {
            _semiTransBrush.Dispose();
        }
        ~CustomRichTextBox()
        {
            _semiTransBrush.Dispose();
        }
        //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        // Enum: EditMessages()
        // It does what?: Contains Windows Messages for API
        // Notes: I only included a few messages here.
        //  If you want more, e-mail me and I'll send you
        //  about 100 of 'em. aaron@thecollective4.com
        public enum EditMessages : int
        {
            EM_LINEINDEX = 0x000000bb,
            EM_LINEFROMCHAR = 0x000000c1,//201,
            EM_FIRSTVISIBLELINE = 0x000000ce,//206,
            EM_CharFromPos = 0x000000d7,//215,
            EM_LINESCROLL = 0x000000b6,//?
            EM_GETLINECOUNT = 0x000000ba,
            EM_PosFromChar = 0x00000426//1062
        }
        //private const long EM_LINEFROMCHAR = 0XC9;

        [DllImport("user32")]
        private static extern IntPtr GetDC(IntPtr hWnd);

        [DllImport("user32")]
        private static extern int ReleaseDC(IntPtr hWnd, IntPtr hdc);

        [DllImport("user32.dll")]
        static extern bool LockWindowUpdate(IntPtr hWndLock);

        private const int WM_PAINT = 0xF;
        private const int WM_NCPAINT = 0x85;
        private const int WM_ACTIVATE = 0x0006;
        private const int WM_NCACTIVATE = 0x0086;

        protected override void OnSelectionChanged(EventArgs e)
        {
            base.OnSelectionChanged(e);
            if (ParentForm != null && SelectionLength == 0 && ParentForm.IsReady)
                UpdateCaretPos();
            this.Invalidate();
        }
        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)]
        public static extern IntPtr SendMessage(HandleRef hWnd, EditMessages Msg, IntPtr wParam, IntPtr lParam);
        //private static uint EM_LINEINDEX = 0xbb;
        #region  Misc Functions

        //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        // Function: FirstVisibleLine()
        // It does what?: Uses API to determine the first
        //  line in a Textbox or RichTextbox control
        // Notes: Return value is zero based.
        internal int FirstVisibleLine()
        {
            if (HandleRef.ToIntPtr(_hrTxtTranslated) == IntPtr.Zero)
                _hrTxtTranslated = new HandleRef(this, this.Handle);
            //return (int)SendMessage(_hrTxtTranslated, EM_LINEINDEX, (IntPtr)(-1), IntPtr.Zero);
            return (int)SendMessage(_hrTxtTranslated, EditMessages.EM_FIRSTVISIBLELINE, IntPtr.Zero, IntPtr.Zero);
        }

        //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        // Function: LastVisibleLine()
        // It does what?: Returns the number of lines in
        //  a Textbox or RichTextbox control by dividing the 
        //  height of the Textbox, by the FontHeight
        // Notes: By dividing, some errors can occur. Example,
        //  if the Txb has only three real lines, when we divide,
        //  it may return 5 when we divide.
        internal int LastVisibleLine()
        {

            int lastLine = FirstVisibleLine() + (this.Height / this.Font.Height);

            if (lastLine > this.Lines.Length | lastLine == 0)
            {
                lastLine = this.Lines.Length;
            }
            return lastLine;
        }
        #endregion
        private HandleRef _hrTxtTranslated;
        int _prevLine = -1;
        internal void UpdateCaretPos()
        {

            int line, col, index;
            // get the current line
            try
            {
                index = this.SelectionStart;
                line = this.GetLineFromCharIndex(index);
                if (_prevLine == line)
                    return;
                _prevLine = line;
                //Managed way to get the column
                //Unmanaged way to get the column
                if (HandleRef.ToIntPtr(_hrTxtTranslated) == IntPtr.Zero)
                    _hrTxtTranslated = new HandleRef(this, this.Handle);
                col = index - (int)SendMessage(_hrTxtTranslated, EditMessages.EM_LINEINDEX, (IntPtr)(-1), IntPtr.Zero);
                // finally, update the display in the status bar, incrementing the line and
                // column values so that the first line & first character position is
                // shown as "1, 1"

                int firstVisibleLine = (int)SendMessage(_hrTxtTranslated, EditMessages.EM_FIRSTVISIBLELINE, IntPtr.Zero, IntPtr.Zero);
                int lastVisibleLine = firstVisibleLine + (this.Height / this.Font.Height);

                //if (lastVisibleLine > this.Lines.Length | lastVisibleLine == 0)
                //{
                //    lastVisibleLine = this.Lines.Length;
                //}
                ParentForm.SynchronizeText(line, col, firstVisibleLine);
            }
            catch //(Exception ex)
            {

                Console.Beep();
            }
        }

        //private void SynchronizeText(int line, int col)
        //{
        //    int index = this.SelectionStart;
        //    ParentForm._lblStatus.Text = string.Format("Line:{0} Column:{1}", line + 1, col + 1);
        //    int i;
        //    for (i = 0; i < ParentForm._markersTranslation.Count; i++)
        //    {
        //        if (index <= ParentForm._markersTranslation[i].destination)
        //            break;
        //    }

        //    int pageshift = index;
        //    if (i > 0)
        //    {
        //        pageshift = index - ParentForm._markersTranslation[i - 1].destination;
        //        i--;
        //    }
        //    else
        //        pageshift = index;
        //    if (_prevLine == line)
        //        return;// no need to navigate
        //    _prevLine = line;
        //    this.Invalidate();
        //    if (index > ParentForm._markersTranslation[ParentForm._markersTranslation.Count - 1].destination)
        //        ParentForm.Navigate(ParentForm._markersTranslation.Count - 2,
        //            ParentForm._markersTranslation[ParentForm._markersTranslation.Count - 1].destination - ParentForm._markersTranslation[ParentForm._markersTranslation.Count - 2].destination);
        //    else
        //        ParentForm.Navigate(i, pageshift);
        //}
        private string _overlayedText;

        public string OverlayedText
        {
            set { _overlayedText = value; }
        }

        private void PaintOverlayedLanguage()
        {
            if (DesignMode || ParentForm == null)
                return;
            IntPtr hDC = IntPtr.Zero;
            hDC = GetDC(this.Handle);
            if (hDC == IntPtr.Zero)
                return;
            Graphics gfx = null;
            try
            {
                gfx = Graphics.FromHdc(hDC);
                if (gfx == null)
                    return;
                //TextFormatFlags flags = TextFormatFlags.VerticalCenter | TextFormatFlags.HorizontalCenter | TextFormatFlags.SingleLine;
                StringFormat _strformat = new StringFormat(StringFormatFlags.NoClip);
                _strformat.Alignment = StringAlignment.Center;
                gfx.DrawString(_overlayedText, _fontOverlay, _semiTransBrush,
                    new RectangleF(new Point(ClientRectangle.X, ClientRectangle.Y + ClientRectangle.Height / 2), new SizeF(ClientRectangle.Width, ClientRectangle.Height / 2)),
                    _strformat);
                //        TextRenderer.DrawText(e.Graphics, "This is some text.", this.Font,
                //new Point(10, 10), Color.White, Color.SteelBlue, TextFormatFlags.Default);
            }
            finally
            {
                if (gfx != null)
                    gfx.Dispose();
                ReleaseDC(this.Handle, hDC);
            }



        }
        int _lineCount = -1;
        protected override void OnTextChanged(EventArgs e)
        {
            base.OnTextChanged(e);
            if (this.Lines.Length < _lineCount)
                this.Invalidate();
            _lineCount = Lines.Length;
        }
        protected override void OnVScroll(EventArgs e)
        {
            base.OnVScroll(e);
            this.Invalidate();
        }
        protected override void OnSizeChanged(EventArgs e)
        {
            base.OnSizeChanged(e);
        }
        protected override void WndProc(ref Message m)
        {
            base.WndProc(ref m);
            // Process messages for overpainting
            switch (m.Msg)
            {

                case WM_PAINT://unfortunately OnPaint is not triggered
                    PaintOverlayedLanguage();
                    break;
                case WM_NCPAINT:
                    break;
                case WM_NCACTIVATE:
                    break;
            }
        }
    }
    public interface IUIMisc : IWin32Window
    {
        void SynchronizeText(int line, int col, int firstVisibleLine);
        string ToLanguage
        {
            get;
        }
        bool IsReady
        {
            get;
        }
        // bool PersistData(bool bCheckVersion);
    }
}
